[Back]

Rhapsody Developer Release Copyright © 1998 by Apple Computer, Inc. All Rights Reserved.

Rhapsody Developer Release Notes:
Thread Support in the Application Kit

This release note provides some guidelines for writing a threaded application in the current release (Developer Release 2) of Rhapsody. For a related discussion, see Thread-safe Classes in Foundation in the release notes for the Foundation framework.

 

General Guidelines

  1. In general, immutable objects are thread-safe. Once you create them, you can safely pass these objects to and from threads. On the other hand, mutable objects are not thread-safe. To use mutable objects in a threaded application, the application must synchronize appropriately (see "Foundation Collection Classes," below).

  2. To safely use the Application Kit in drawing operations involving multiple threads, you must ensure that all threads have their own NSDPSContext. (Use the convenience function [NSApplication detachDrawingThread:] to create a thread with its own graphics context).

  3. If a thread is going to draw in a view, bracket all drawing code between the NSView methods lockFocus and unlockFocus (just as in the single-threaded case).

  4. The main thread of the application is responsible for handling events; the main thread is the one blocked in [NSApplication run] calling [NSApplication nextEventMatchingMask: ...] and [NSApplication sendEvent:]. While the Application Kit continues to work if other threads are involved in the event path, operations can occur out of sequence. For example if two different threads are responding to key events, the keys could be received out of order. By letting the main thread process events, you achieve a more consistent user experience. The main thread can send a message (DO, Mach message, or through a NSConditionLock) to get another thread to do some work.

  5. Multithreading doesn't come for free. Percieved performance gains might actually be improved due to deferred execution. However, raw performance on a single-processor system will be somewhat slower for multi-threaded applications. This is due to the overhead associated with proper synchronization (locking, messaging, and so on). Consider carefully whether you need to use threads. Many problems can be solved by deferring executing using timers (NSTimer).

Note: The Java Yellow Box API implementation (including AWT) is multi-threaded.

The major reward from multi-threading your application is improved performance on a multi-processor system.

 

Subsystem Notes

NSDPSContext

Only one thread can use an NSDPSContext at a time. A thread can use multiple NSDPSContexts during its lifetime, but if two threads access a single NSDPSContext at the same time, unpredictable results can occur.

Flushing still occurs automatically after every event in the main thread of the application. However, there are times when you need to explicitly flush when drawing on other threads. Use the flush: and flushGraphics: methods of NSDPSContext to flush the stream buffer and window backing store, respectively.

 

NSImage

One thread can create an image, draw it, pass it off to another thread, which can then draw it, and so forth. The underlying image cache is shared among all threads.

 

NSView

You can create, destroy, resize, move, and perform other operations with NSViews from different threads. The system ensures that all drawing is deferred while these operations occur (through the lockFocus and unlockFocus mentioned above).

In a multi-threaded application, the main thread is still responsible for redisplaying dirty views through the same process as a single-threaded application. The drawRect: method of every dirty view is called in the main thread. If the drawing needs to be done on another thread, the drawRect: method for the view should arrange for secondary thread to do the drawing and not do any drawing in drawRect:.

If a secondary thread of an application wants to cause portions of the view to be redrawn on the main thread, the normal mechanisms work (setNeedsDisplay:, setNeedsDisplayInRect:, and setViewsNeedDisplay:).

Performance Note: The view system's gstates are per-thread. Using gstates in a multi-threaded application improves drawing performance in a way similar to a single-threaded application. Please use the public method (gstate) to get at the gstate of the current thread.

 

Foundation Collection Classes

The collection classes in Foundation (for example, NSMutableArray, NSMutableDictionary) are not thread-safe. You must lock around them to use them in a multi-threaded application:

Not Thread-Safe:
thread-1 thread-2

count = [array count];
for (i = 0; i < count; i++) { 
    [array addObject:newObject];
    if ([array objectAtIndex:i] == obj) {
        // ... do something ...
    }
}

In this example, if thread-1 were preempted while it iterated over the array, the second thread, thread-2 , could be scheduled and add an object to the array. When the first thread, thread-1, ran again, it's count variable would be stale, and it may not find the desired object.

Thread-Safe :
thread-1 thread-2

[arrayLock lock];
count = [array count];
for (i = 0; i < count; i++) { 
    [arrayLock lock];
    if ([array objectAtIndex:i] == obj) { 
        [array addObject:newObject];
        // ... do something ...  
        [arrayLock unlock];
    }
}
[arrayLock unlock];

This is the same example using locks around the array manipulation. The first thread will complete its entire iteration before the second thread can access the array.

Note: See Foundation release notes for more information on Foundation thread support.

 

Known Problems and Limitations

The Window System (NSWindow, NSPanel) is not thread-safe. The workaround in this release is to let the windows be created by the main thread (threads could be created in a view's init or awakeFromNib: methods).

Thread termination can cause some drawing problems. In general, the Application Kit doesn't know if your application is using threads to draw or do similar operations. Before dimissing a window, you should ensure that your threads have stopped drawing.

Using locks within drawRect: can cause deadlock problems if they are held beyond the drawRect:. It is safe to lock and unlock NSLocks within a drawRect:.